#ifndef __AST_H__
#define __AST_H__
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <unordered_map>

struct Ast {
    static std::shared_ptr<Ast> nullAst;
    static int level;
    static int dump;
    virtual ~Ast();
    static void dumpAst(const std::shared_ptr<Ast> &root);
    static double eval(const std::shared_ptr<Ast> &root);
};

struct Statement : public Ast {
    std::shared_ptr<Ast> stmt;
    Statement(const std::shared_ptr<Ast> &ast) : stmt(ast) {}
};

struct StatementList : public Ast {
    std::vector<std::shared_ptr<Ast>> list;
    StatementList(const std::shared_ptr<Ast> &ast) {
        if (auto x = std::dynamic_pointer_cast<StatementList>(ast)) {
            for (auto &stmt : x->list) {
                list.emplace_back(stmt);
            }
        } else {
            list.emplace_back(ast);
        }
    }
    friend std::shared_ptr<StatementList>
    operator+(const std::shared_ptr<StatementList> &stmtList,
              const std::shared_ptr<Statement> &stmt) {
        auto stmtMerge = std::make_shared<StatementList>(stmtList);
        stmtMerge->list.emplace_back(stmt);
        return stmtMerge;
    }
};

struct Expression : public Ast {
    std::shared_ptr<Ast> left;
    std::shared_ptr<Ast> right;
    int type;
    std::shared_ptr<Ast> next;
    Expression(const int type, const std::shared_ptr<Ast> &left,
               const std::shared_ptr<Ast> &right)
        : type(type), left(left), right(right) {}
    static std::unordered_map<int, std::string> type2String;
};

struct ExpressionList : public Ast {
    std::vector<std::shared_ptr<Ast>> list;
    ExpressionList(const std::shared_ptr<Ast> &ast) {
        if (auto x = std::dynamic_pointer_cast<ExpressionList>(ast)) {
            for (auto &exp : x->list) {
                list.emplace_back(exp);
            }
        } else {
            list.emplace_back(ast);
        }
    }
    friend std::shared_ptr<ExpressionList>
    operator+(const std::shared_ptr<ExpressionList> &expList,
              const std::shared_ptr<Expression> &exp) {
        auto expMerge = std::make_shared<ExpressionList>(expList);
        expMerge->list.emplace_back(exp);
        return expMerge;
    }
};

struct SymbolList;

struct Symbol : public Ast {
    std::string name;
    double value;
    std::shared_ptr<Ast> func;
    std::shared_ptr<SymbolList> param;
    Symbol(const std::string &name, const double value,
           const std::shared_ptr<Ast> &func)
        : name(name), value(value), func(func) {}
    static std::unordered_map<std::string, std::shared_ptr<Symbol>> symtab;
    static std::shared_ptr<Symbol> lookup(const std::string &name) {
        if (symtab.find(name) == symtab.end()) {
            symtab[name] = std::make_shared<Symbol>(name, 0, nullAst);
        }
        return symtab[name];
    }
};

struct SymbolList : public Ast {
    std::vector<std::shared_ptr<Ast>> list;
    SymbolList(const std::shared_ptr<Ast> &ast) {
        if (auto x = std::dynamic_pointer_cast<SymbolList>(ast)) {
            for (auto &sym : x->list) {
                list.emplace_back(sym);
            }
        } else {
            list.emplace_back(ast);
        }
    }
    friend std::shared_ptr<SymbolList>
    operator+(const std::shared_ptr<SymbolList> &symList,
              const std::shared_ptr<Symbol> &sym) {
        auto symMerge = std::make_shared<SymbolList>(symList);
        symMerge->list.emplace_back(sym);
        return symMerge;
    }
};

struct Fncall : public Ast {
    enum bifs { B_sqrt = 1, B_exp, B_log, B_print };
    std::shared_ptr<ExpressionList> param;
    enum bifs functype;
    Fncall(const enum bifs functype,
           const std::shared_ptr<ExpressionList> &param)
        : functype(functype), param(param) {}
};

struct Ufncall : public Ast {
    std::shared_ptr<ExpressionList> param;
    std::shared_ptr<Symbol> s;
    Ufncall(const std::shared_ptr<Symbol> &s,
            const std::shared_ptr<ExpressionList> &param)
        : s(s), param(param) {}
};

struct Flow : public Ast {
    enum flowType { WHILE, IF };
    std::shared_ptr<Ast> cond;
    std::shared_ptr<Ast> tl;
    std::shared_ptr<Ast> el;
    flowType ft;
    Flow(const flowType ft, const std::shared_ptr<Ast> &cond,
         const std::shared_ptr<Ast> &tl, const std::shared_ptr<Ast> &el)
        : ft(ft), cond(cond), tl(tl), el(el) {}
};

struct Numval : public Ast {
    double number;
    Numval(const double number) : number(number) {}
};

#endif